package Tools;
import Entities.Category;
import Entities.Medium;
import Managers.CategoryManager;
import Managers.LibraryManager;
import Managers.MediumManager;
import org.apache.commons.io.FileExistsException;

import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.jopendocument.dom.OOUtils;
import org.jopendocument.dom.spreadsheet.Sheet;
import org.jopendocument.dom.spreadsheet.SpreadSheet;

/**
 * Created by Kamil Triščík on 05. 6. 2014.
 * author: Kamil Triščík
 * version: 2014-06-05
 */
public class ODSWorker {
    private CategoryManager categoryManager = null;
    private MediumManager mediumManager = null;
    private LibraryManager libraryManager = null;

    /**
     * Constructor
     *
     * @param categoryManager - manager for work with categories
     * @param mediumManager - manager for work with media
     * @param libraryManager - manager for connect media and categories
     */
    public ODSWorker(CategoryManager categoryManager, MediumManager mediumManager,
                     LibraryManager libraryManager) {
        if(categoryManager == null) {
            throw new NullPointerException("CATEGORY MANAGER argument is null!");
        }
        if(mediumManager == null) {
            throw new NullPointerException("MEDIUM MANAGER argument is null!");
        }
        if(libraryManager == null) {
            throw new NullPointerException("LIBRARY MANAGER argument is null!");
        }
        this.mediumManager = mediumManager;
        this.categoryManager = categoryManager;
        this.libraryManager = libraryManager;
    }

    /**
     * Thi check if current ods file is valid
     *
     * @param filename - path of ods file to validate
     * @return true - file is valid
     *         false - file is not valid
     */
    public boolean isODFValid(String filename) {
        System.out.println("path= " + filename);
        if(filename == null) {
            throw new NullPointerException("Argument \"filename\" is NULL");
        }
        if(!filename.endsWith(".ods")) {
            return false;
        }
        if(!new File(filename).exists()) {

            return false;
        }
        return validateFile(filename) && validateData(new File(filename));
    }

    /**
     * Thi check if current ods file is valid
     *
     * @param file -  ods file to validate
     * @return true - file is valid
     *         false - file is not valid
     */
    public boolean isODFValid(File file) {
        if(file == null) {
            throw new NullPointerException("Argument \"file\" is NULL");
        }
        if(!file.getAbsolutePath().endsWith(".ods")) {
            return false;
        }
        if(!file.exists()) {
            return false;
        }
        return validateFile(file.getAbsolutePath()) && validateData(file);
    }

    /**
     * This method check if current ods file is valid
     * Check if ods file contains necessary files
     *
     * @param filename - path of ods file for validate
     * @return true - file is valid
     *         false - file is not valid
     */
    private boolean validateFile(String filename) {
        int validNumber = 0;
        String OS = System.getProperty("os.name").toLowerCase();
        try{
            //get the zip file content
            ZipInputStream zis =
                    new ZipInputStream(new FileInputStream(filename));
            //get the zipped file list entry
            ZipEntry ze = zis.getNextEntry();
            String folderPath;
            if(OS.indexOf("win") >= 0) {
            while(ze!=null){
                String fileName = ze.getName();
                File newFile = new File(File.separator + fileName);
                System.out.println(newFile.getAbsoluteFile().toString());
                if(newFile.getAbsoluteFile().toString().contains("\\mimetype")) {
                    validNumber++;
                }
                else if(newFile.getAbsoluteFile().toString().contains("\\content.xml")) {
                    validNumber++;
                }
                else if(newFile.getAbsoluteFile().toString().contains("\\styles.xml")) {
                    validNumber++;
                }
                else if(newFile.getAbsoluteFile().toString().contains("\\META-INF\\manifest.xml")) {
                    validNumber++;
                }
                ze = zis.getNextEntry();
            }
            }
            else {
                while(ze!=null){
                    String fileName = ze.getName();
                    File newFile = new File(File.separator + fileName);
                    if(newFile.getAbsoluteFile().toString().equals("/mimetype")) {
                        validNumber++;
                    }
                    else if(newFile.getAbsoluteFile().toString().equals("/content.xml")) {
                        validNumber++;
                    }
                    else if(newFile.getAbsoluteFile().toString().equals("/styles.xml")) {
                        validNumber++;
                    }
                    else if(newFile.getAbsoluteFile().toString().equals("/META-INF/manifest.xml")) {
                        validNumber++;
                    }
                    ze = zis.getNextEntry();
                }
            }

            zis.closeEntry();
            zis.close();
        }catch(IOException ex){
            ex.printStackTrace();
        }
        return validNumber == 4;
    }

    /**
     * This method check if data of current ods file are valid
     *
     * @param file - path of ods file for validate
     * @return true - data are valid
     *         false - date are not valid
     */
    private boolean validateData(File file) {
        try {
            Sheet sheet = null;
            int sheetCount = -1;
            sheetCount = SpreadSheet.createFromFile(file).getSheetCount();
            for(int i = 0; i < sheetCount; i++) {
                Category category = new Category();
                sheet = SpreadSheet.createFromFile(file).getSheet(i);
                category.setName(sheet.getName());
                /////////////////////////////////////////////////////////////////
                TableModel model = sheet.getTableModel(0,0);
                int j = 1;
                while ((j < sheet.getRowCount()) && (!model.getValueAt(j,0).equals(""))) {
                    Medium medium = new Medium();
                    medium.setLabel(model.getValueAt(j, 1).toString());
                    medium.setType(model.getValueAt(j, 2).toString());
                    j++;
                    List<String> movies = new ArrayList<>();
                    while ((j < sheet.getRowCount()) && (!model.getValueAt(j,0).equals(""))
                            && (model.getValueAt(j, 2).toString().equals(""))) {
                        movies.add(model.getValueAt(j,1).toString());
                        j++;
                    }
                    medium.setMovies(movies);
                }
            }
            return true;
        }
        catch (Exception e) {
            return false;
        }
    }

    /**
     * This method import data from *.ods file
     *
     * @param filename - path of file from import
     * @throws FileExistsException - when error occurs while parsing file
     */
    public void importFromODF(String filename) throws FileExistsException{
        if(filename == null) {
            throw new NullPointerException("Parameter FILENAME si null");
        }
        File input = new File(filename);
        if(!input.exists()) {
            throw new FileExistsException("File doesn't exist");
        }
        importCore(input);
    }

    /**
     * This method import data from *.ods file
     *
     * @param file - file for import
     */
    public void importFromODF(File file) {
        if(file == null) {
            throw new NullPointerException("Parameter FILE is null");
        }
        importCore(file);
    }

    /**
     * This method import data from *.ods file
     *
     * @param input - file for import
     */
    private void importCore(File input) {
        Sheet sheet = null;
        int sheetCount = -1;
        try{
            sheetCount = SpreadSheet.createFromFile(input).getSheetCount();
        } catch (IOException ex) {
            Logger.getLogger(ODSWorker.class.getName()).log(Level.SEVERE, null, ex);
        }
        for(int i = 0; i < sheetCount; i++) {
            Category category = new Category();
            try {
                sheet = SpreadSheet.createFromFile(input).getSheet(i);
                category.setName(sheet.getName());

            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                categoryManager.createCategory(category);//CREATE NEW CATEGORY
            } catch(IllegalArgumentException e) {
                System.out.println("Category with name" + category.getName() + "already exist");
            } catch (DatabaseException e) {
                e.printStackTrace();
            }
            /////////////////////////////////////////////////////////////////
            List<String> mediaID = new ArrayList<>();
            TableModel model = sheet.getTableModel(0,0);
            int j = 1;
            while ((j < sheet.getRowCount()) && (!model.getValueAt(j,0).equals(""))) {
                Medium medium = new Medium();
                medium.setLabel(model.getValueAt(j, 1).toString());
                medium.setType(model.getValueAt(j, 2).toString());
                j++;
                List<String> movies = new ArrayList<>();
                while ((j < sheet.getRowCount()) && (!model.getValueAt(j,0).equals(""))
                        && (model.getValueAt(j, 2).toString().equals(""))) {
                    movies.add(model.getValueAt(j,1).toString());
                    j++;
                }
                medium.setMovies(movies);
                try {
                    mediumManager.createMedium(medium);//CREATE NEW MEDIUM
                    mediaID.add(medium.getId());
                } catch (DatabaseException e) {
                    e.printStackTrace();
                }
                catch(IllegalArgumentException e) {
                    System.out.println("Medium with name \"" + medium.getLabel() + "\" already exist");
                }
            }
            ///////////////////////////////
            for(String id : mediaID) {
                try {
                    libraryManager.insertMediumIntoCategory(mediumManager.getMediumById(id), category);//INSERT MEDIA IDs INTO CATEGORY
                } catch (DatabaseException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * export data from database into ods file
     *
     * @param filename - path of output *.ods file
     * @param categories - list of categories from the database
     */

    public void exportToODF(String filename, List<Category> categories) {
        if(filename == null) {
            throw new NullPointerException("Parameter FILENAME si null");
        }
        if(categories == null) {
            throw new NullPointerException("Parameter MEDIA is null");
        }
        File output = new File(filename);
        export(output, categories);
    }

    /**
     * export data from database into ods file
     *
     * @param output -  output file
     * @param categories - list of categories from the database
     */
    public void exportToODF(File output, List<Category> categories) {
        if(output == null) {
            throw new NullPointerException("Parameter FILENAME si null");
        }
        if(categories == null) {
            throw new NullPointerException("Parameter MEDIA is null");
        }
        export(output, categories);
    }

    /**
     * This method create new file for export.
     * Create sheets of SpreadSheet
     * Call method setSheet(...) which initialize sheet
     *
     * @param output -  output file
     * @param categories - list of categories from the database
     */
    private void export(File output, List<Category> categories) {
        DefaultTableModel model = new DefaultTableModel(3, 0);
        Sheet sheet = null;
        try {
            SpreadSheet.createEmpty(model).saveAs(output);
        } catch (IOException e) {
            e.printStackTrace();
        }
        int i = 0;
        for(Category category : categories) {
            if(i == 0) {
                try {
                    sheet = SpreadSheet.createFromFile(output).getSheet(0);
                    sheet.setName(category.getName());
                    final Sheet newSheet = sheet.getSpreadSheet().getSheet(0);
                    setSheet(newSheet, category);
                    sheet.getSpreadSheet().saveAs(output);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            else {
                try {
                    sheet = SpreadSheet.createFromFile(output).getSheet(0);
                    sheet.getSpreadSheet().addSheet(i,category.getName());
                    sheet.getSpreadSheet().saveAs(output);
                    final Sheet newSheet = sheet.getSpreadSheet().getSheet(i);
                    setSheet(newSheet, category);
                    newSheet.getSpreadSheet().saveAs(output);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            i++;
        }
        try {
            OOUtils.open(output);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * This method initialize sheet
     *
     * @param sheet - sheet tu update
     * @param category - category for current category
     */
    private void setSheet(Sheet sheet, Category category) {
        char[] alphabet = new String("abcdefghijklmnopqrstuvwxyz").toUpperCase().toCharArray();
        int i = 0;
        sheet.setRowCount(500);
        sheet.setColumnCount(10);
        sheet.setValueAt(new String("-"),0,i);
        sheet.setValueAt(new String("Label/Title"),1,i);
        sheet.setValueAt(new String("Type"),2,i);
        i++;
        int k = 0;
        List<String> mediaID = category.getMediums();
        for(String ID : mediaID) {
            String mSign = "";
            if(mediaID.size()/26 != 0) {
                mSign += mediaID.size()/26;
            }
            mSign += alphabet[k % 26];

            Medium medium = null;
            try {
                medium = mediumManager.getMediumById(ID);
            } catch (DatabaseException e) {
                e.printStackTrace();
            }
            sheet.setValueAt(mSign,0,i);
            sheet.setValueAt(medium.getLabel(), 1,i);
            sheet.setValueAt(medium.getType(),2,i);
            i++;
            int j = 1;
            for(String movie : medium.getMovies()) {
                sheet.setValueAt(j,0,i);
                sheet.setValueAt(movie, 1,i);
                i++;
                j++;
            }
            k++;
        }
    }
}